Unity大世界LightMap处理

Unity3D 知识积累 专栏收录该内容
27 篇文章 3 订阅

在Unity中,烘焙LightMap采用的是一个场景烘焙一组LightMap。而对于大世界场景来说,没办法把世界上所有的物体在同一场景下烘焙。Unity提供的解决办法是通过SubScene来解决,就是分场景烘焙,然后再通过加载卸载Scene的方式来实现。但有时候有这样的需求,同一组室内场景可能在多个地方存在,美术希望烘焙好一组物体,能复制到各个地方,并且能很好地预览,这样使用SubScene来说就比较麻烦了。

先说一下 unity的LightMap机制,烘焙分为动态物体和静态物体,动态物体走的是GI,通过环境光,LightProb等这些算出三维光照数据,然后计算动态物体的球谐光照。对于静态物体来说就会烘焙成 lightmap,一般有几组三张贴图(color,dir以及shadow)。静态物体的MeshRender上会有个lightmapIndex存放采用第几组lightmap,还有个lightmapScaleOffset存放uv偏移,通过这两个数据就能显示正确。

知道LightMap的原理后就比较简单了,我们只需要存好我们需要使用的数据,然后设置对应的位置就能正确显示了。

首先,我们定义好我们的数据结构,我们期望在一个prefab上挂一个我们的脚本,然后加载这个prefab上所有的MeshRender。我们就需要一个这样的ScriptObject。

  1. public class CustomLightMapDataMap : ScriptableObject
  2. {
  3. public MeshLightmapData[] LightMapDatas = null;
  4. }
  5. [Serializable]
  6. public struct CustomLightmapData
  7. {
  8. /// <summary>
  9. /// The color for lightmap.
  10. /// </summary>
  11. public Texture2D LightmapColor;
  12. /// <summary>
  13. /// The dir for lightmap.
  14. /// </summary>
  15. public Texture2D LightmapDir;
  16. /// <summary>
  17. /// The shadowmask for lightmap.
  18. /// </summary>
  19. public Texture2D ShadowMask;
  20. /// <summary>
  21. /// Initializes a new instance of the <see cref="CustomLightmapData"/> struct.
  22. /// </summary>
  23. /// <param name="data">lightmapdata.</param>
  24. public CustomLightmapData(LightmapData data)
  25. {
  26. this.LightmapColor = data.lightmapColor;
  27. this.LightmapDir = data.lightmapDir;
  28. this.ShadowMask = data.shadowMask;
  29. }
  30. public bool IsA(LightmapData data)
  31. {
  32. return this.LightmapColor == data.lightmapColor &&
  33. this.LightmapDir == data.lightmapDir &&
  34. this.ShadowMask == data.shadowMask;
  35. }
  36. public LightmapData GetLightmapData()
  37. {
  38. LightmapData data = new LightmapData();
  39. data.lightmapColor = this.LightmapColor;
  40. data.lightmapDir = this.LightmapDir;
  41. data.shadowMask = this.ShadowMask;
  42. return data;
  43. }
  44. }
  45. [Serializable]
  46. public struct MeshLightmapData
  47. {
  48. public Vector4 LightmapScaleOffset;
  49. public CustomLightmapData LightmapData;
  50. }

然后再在编辑器上弄一个菜单,选中物体就能自动干这件事情。

  1. [MenuItem("Window/LightMapGenerate")]
  2. private static void Generated()
  3. {
  4. string outputPath = "Assets/LightMapPrefab";
  5. var lightmapPath = GetLightMapPath();
  6. if (!string.IsNullOrEmpty(lightmapPath))
  7. {
  8. outputPath = Path.GetDirectoryName(lightmapPath);
  9. }
  10. GameObject obj = Selection.activeGameObject;
  11. if (obj == null)
  12. {
  13. return;
  14. }
  15. var dataMap = (CustomLightMapDataMap)ScriptableObject.CreateInstance(typeof(CustomLightMapDataMap));
  16. var renders = obj.GetComponentsInChildren<MeshRenderer>();
  17. List<MeshLightmapData> datas = new List<MeshLightmapData>();
  18. var lightmaps = LightmapSettings.lightmaps;
  19. foreach (var render in renders)
  20. {
  21. if (render.lightmapIndex < 0 || render.lightmapIndex >= lightmaps.Length)
  22. {
  23. Debug.LogError("lightmap error:" + render.gameObject.name);
  24. return;
  25. }
  26. var data = new MeshLightmapData()
  27. {
  28. LightmapScaleOffset = render.lightmapScaleOffset,
  29. LightmapData = new CustomLightmapData(lightmaps[render.lightmapIndex]),
  30. };
  31. datas.Add(data);
  32. }
  33. dataMap.LightMapDatas = datas.ToArray();
  34. var loader = obj.GetComponent<LightMapDataLoader>();
  35. if (loader == null)
  36. {
  37. loader = obj.AddComponent<LightMapDataLoader>();
  38. }
  39. outputPath = Path.Combine(outputPath, obj.name + ".asset");
  40. AssetDatabase.CreateAsset(dataMap, outputPath);
  41. AssetDatabase.SaveAssets();
  42. loader.Asset = AssetDatabase.LoadAssetAtPath<CustomLightMapDataMap>(outputPath);
  43. }
  44. private static string GetLightMapPath()
  45. {
  46. var lightmaps = LightmapSettings.lightmaps;
  47. if (lightmaps.Length == 0)
  48. {
  49. return string.Empty;
  50. }
  51. return AssetDatabase.GetAssetPath(lightmaps[0].lightmapColor);
  52. }

数据保存好了,我们只需要加载就好了。加载除了要加载MeshRender上的数据,还要设置好场景的LightMap。这里还有个特别重要的问题就是卸载,在物体销毁时,我们要处理场景的lightmap,这里需要通过一个计数器去干这件事情,当引用计数为0了,我们就去清理lightmap贴图数据。

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.SceneManagement;
  5. [ExecuteInEditMode]
  6. public class LightMapDataLoader : MonoBehaviour
  7. {
  8. private static Dictionary<CustomLightmapData, int> lightmapDataRefenceCount = new Dictionary<CustomLightmapData, int>();
  9. [SerializeField]
  10. private CustomLightMapDataMap asset;
  11. private HashSet<CustomLightmapData> lightmapDatas = new HashSet<CustomLightmapData>();
  12. public CustomLightMapDataMap Asset
  13. {
  14. get { return this.asset; }
  15. set { this.asset = value; }
  16. }
  17. public static void Clear()
  18. {
  19. lightmapDataRefenceCount.Clear();
  20. }
  21. // Start is called before the first frame update
  22. private void Awake()
  23. {
  24. if (this.asset != null)
  25. {
  26. var lightmaps = LightmapSettings.lightmaps;
  27. var renders = this.GetComponentsInChildren<MeshRenderer>();
  28. var datas = this.asset.LightMapDatas;
  29. if (datas.Length != renders.Length)
  30. {
  31. return;
  32. }
  33. List<LightmapData> lightmapList = new List<LightmapData>(lightmaps);
  34. for (int i = 0; i < datas.Length; i++)
  35. {
  36. var lightMapIndex = -1;
  37. var nullIndex = -1;
  38. LightmapData currentData = null;
  39. for (int j = lightmapList.Count - 1; j >= 0; j--)
  40. {
  41. var lightmap = lightmapList[j];
  42. if (datas[i].LightmapData.IsA(lightmap))
  43. {
  44. lightMapIndex = j;
  45. currentData = lightmap;
  46. }
  47. if (lightmap.lightmapColor == null &&
  48. lightmap.lightmapDir == null &&
  49. lightmap.shadowMask == null)
  50. {
  51. nullIndex = j;
  52. }
  53. }
  54. if (lightMapIndex == -1)
  55. {
  56. currentData = datas[i].LightmapData.GetLightmapData();
  57. if (nullIndex == -1)
  58. {
  59. lightmapList.Add(currentData);
  60. lightMapIndex = lightmapList.Count - 1;
  61. }
  62. else
  63. {
  64. lightmapList[nullIndex] = currentData;
  65. lightMapIndex = nullIndex;
  66. }
  67. }
  68. this.lightmapDatas.Add(datas[i].LightmapData);
  69. renders[i].lightmapIndex = lightMapIndex;
  70. renders[i].lightmapScaleOffset = datas[i].LightmapScaleOffset;
  71. }
  72. foreach (var data in this.lightmapDatas)
  73. {
  74. if (!lightmapDataRefenceCount.TryGetValue(data, out var count))
  75. {
  76. count = 0;
  77. }
  78. else
  79. {
  80. lightmapDataRefenceCount.Remove(data);
  81. }
  82. count++;
  83. lightmapDataRefenceCount.Add(data, count);
  84. }
  85. LightmapSettings.lightmaps = lightmapList.ToArray();
  86. }
  87. }
  88. private void OnDestroy()
  89. {
  90. foreach (var data in this.lightmapDatas)
  91. {
  92. if (lightmapDataRefenceCount.TryGetValue(data, out var count))
  93. {
  94. count--;
  95. lightmapDataRefenceCount.Remove(data);
  96. if (count == 0)
  97. {
  98. var lightmaps = LightmapSettings.lightmaps;
  99. for (int i = 0; i < lightmaps.Length; i++)
  100. {
  101. if (data.IsA(lightmaps[i]))
  102. {
  103. lightmaps[i].lightmapColor = null;
  104. lightmaps[i].lightmapDir = null;
  105. lightmaps[i].shadowMask = null;
  106. }
  107. }
  108. LightmapSettings.lightmaps = lightmaps;
  109. }
  110. else
  111. {
  112. lightmapDataRefenceCount.Add(data, count);
  113. }
  114. }
  115. }
  116. }
  117. }

做好这些事情之后,我们就可以在场景中烘焙一组物体,然后选中Root,点击Window/LightMapGenerate,会帮你组织好数据,挂好脚本。你可以把这个物体复制到任何地方都是显示正确,也可以保存成prefab通过程序加载和销毁。

  • 0
    点赞
  • 2
    评论
  • 3
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

  • chawuyu1752
    chawuyu1752:这个unity内部能用但是打出包之后lightmap丢失了就不知道咋办- -!7 月前回复举报
  • <
  • 1
  • >
©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值

举报

选择你想要举报的内容(必选)
  • 内容涉黄
  • 政治相关
  • 内容抄袭
  • 涉嫌广告
  • 内容侵权
  • 侮辱谩骂
  • 样式问题
  • 其他
新手
引导
客服 举报 返回
顶部